package com.mj.mes.socket;/**
 * RFID 数据接收
 *
 * @author cRyann
 * @create 2017-05-18 18:52
 **/

import com.serotonin.modbus4j.exception.ModbusTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;

/**
 * RFID 数据接收
 *
 * @author cRyann
 * @create 2017-05-18 18:52
 **/
@Component
public class SocketServer{
    private final Logger log = LoggerFactory.getLogger(SocketServer.class);
    private Map<SocketChannel, List<byte[]>> keepDataTrack = new HashMap<>();
    private ByteBuffer buffer = ByteBuffer.allocate(2 * 1024);

    @Async("SocketServerAsync")
    protected void runSocketServer() throws ModbusTransportException {
        final int DEFAULT_PORT = SocketConfig.SERVER_LISTEN_PORT;// open Selector and ServerSocketChannel
        // by calling the open() method
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel
                     .open()) {
            // check that both of them were successfully opened
            if ((serverSocketChannel.isOpen()) && (selector.isOpen())) {
                // configure non-blocking mode
                serverSocketChannel.configureBlocking(false);
                // set some options
                serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF,
                        256 * 1024);
                serverSocketChannel.setOption(
                        StandardSocketOptions.SO_REUSEADDR, true);
                // bind the server socket channel to port
                serverSocketChannel.bind(new InetSocketAddress(DEFAULT_PORT));
                // register the current channel with the given selector
                serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                // display a waiting message while ... waiting!
                log.info("Waiting for connections ...");
                while (true) {
                    // wait for incomming events
                    selector.select();
                    // there is something to process on selected keys
                    Iterator keys = selector.selectedKeys().iterator();
                    while (keys.hasNext()) {
                        SelectionKey key = (SelectionKey) keys.next();
                        // prevent the same key from coming up again
                        keys.remove();
                        if (!key.isValid()) {
                            continue;
                        }
                        if (key.isAcceptable()) {
                            acceptOP(key, selector);
                        } else if (key.isReadable()) {
                            this.readOP(key);
                        } else if (key.isWritable()) {
                            this.writeOP(key);
                        }
                    }
                }
            } else {
                log.debug("The server socket channel or selector cannot be opened!");
            }
        } catch (IOException ex) {
            System.err.println(ex);
        }
    }

    // isAcceptable returned true
    private void acceptOP(SelectionKey key, Selector selector)
            throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverChannel.accept();
        socketChannel.configureBlocking(false);
        System.out.println("Incoming connection from: "
                + socketChannel.getRemoteAddress());
        // write a welcome message
//        socketChannel.write(ByteBuffer.wrap("Hello!\n".getBytes("UTF-8")));
        // register channel with selector for further I/O
        keepDataTrack.put(socketChannel, new ArrayList<byte[]>());
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    // isReadable returned true
    private void readOP(SelectionKey key) throws ModbusTransportException {
        try {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            buffer.clear();
            int numRead = -1;
            try {
                numRead = socketChannel.read(buffer);
            } catch (IOException e) {
                System.err.println("Cannot read error!");
            }
            if (numRead == -1) {
                this.keepDataTrack.remove(socketChannel);
                System.out.println("Connection closed by: "
                        + socketChannel.getRemoteAddress());
                socketChannel.close();
                key.cancel();
                return;
            }
            byte[] data = new byte[numRead];
            System.arraycopy(buffer.array(), 0, data, 0, numRead);
            String temp = bytesTohex(data);
            log.debug(temp + "<>" + hexToDec(temp.substring(temp.length() - 2)) + " from "
                    + socketChannel.getRemoteAddress());
            String[] address = socketChannel.getRemoteAddress().toString().split(":");
            String rfid = address[0].substring(1);
//            msgAppService.msgAppMaker(rfid, hexToDec(temp.substring(temp.length() - 2)));
//            amqpTemplate.convertAndSend(RabbitmqQueue.RFID_QUEUE, socketChannel.getRemoteAddress()+":"+hexToDec(temp.substring(temp.length()-2)));
            // write back to client
            doEchoJob(key, data);
        } catch (IOException ex) {
            System.err.println(ex);
        }
    }

    // isWritable returned true
    private void writeOP(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        List<byte[]> channelData = keepDataTrack.get(socketChannel);
        Iterator<byte[]> its = channelData.iterator();
        while (its.hasNext()) {
            byte[] it = its.next();
            its.remove();
            socketChannel.write(ByteBuffer.wrap(it));
        }
        key.interestOps(SelectionKey.OP_READ);
    }

    private void doEchoJob(SelectionKey key, byte[] data) {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        List<byte[]> channelData = keepDataTrack.get(socketChannel);
        channelData.add(data);
        key.interestOps(SelectionKey.OP_WRITE);
    }

    private String bytesTohex(byte[] bytes) {
        BigInteger bigInteger = new BigInteger(1, bytes);
        return bigInteger.toString(16);
    }

    private String hexToDec(String hex) {
        BigInteger srch = new BigInteger(hex, 16);
        return srch.toString();
    }
}
